package com.qyb.base.java8.chap5;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.IntSupplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class BuildingStreams {
/*
创建流
集合生成流,根据数值范围创建数值流,从值序列、数组、文件来创建流，甚至由生成函数来创建无限流！
 */
    public static void demo1() throws Exception{
        //1.由值创建流 : 使用静态方法Stream.of，通过显式值创建一个流。它可以接受任意数量的参数。
        Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
        stream.map(String::toUpperCase).forEach(System.out::println);

        // Stream.empty: 使用empty得到一个空流
        Stream<String> emptyStream = Stream.empty();


        //2.由数组创建流:静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。
        int[] numbers = {2, 3, 5, 7, 11, 13};
        System.out.println(Arrays.stream(numbers).sum());

        //3.由文件生成流
        //例如，一个很有用的方法是Files.lines，它会返回一个由指定文件中的各行构成的字符串流。
        long uniqueWords = Files.lines(Paths.get("lambdasinaction/chap5/data.txt"), Charset.defaultCharset())
                .flatMap(line -> Arrays.stream(line.split(" ")))
                .distinct()
                .count();

        System.out.println("There are " + uniqueWords + " unique words in data.txt");
        /*
        可以使用Files.lines得到一个流，其中的每个元素都是给定文件中的一行。然后，你
        可以对line调用split方法将行拆分成单词。应该注意的是，你该如何使用flatMap产生一个扁
        平的单词流，而不是给每一行生成一个单词流。最后，把distinct和count方法链接起来，数
        数流中有多少各不相同的单词。
         */
        long uniqueWords1 = 0;
        try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ // 流会自动关闭
            uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))         //生成单词流
                    .distinct()                                                                 //删除重复项
                    .count();                                                                   //数一数有多少各不相同的单词
        }
        catch(IOException e){                   //如果打开文件时出现异常则加以处理
        }

        //4. 由函数生成流：创建无限流
        /* 两个静态方法来从函数生成流：Stream.iterate和Stream.generate
        这两个操作可以创建所谓的无限流：不像从固定集合创建的流那样有固定大小的流。由iterate
        和generate产生的流会用给定的函数按需创建值，因此可以无穷无尽地计算下去！一般来说，
        应该使用limit(n)来对这种流加以限制，以避免打印无穷多个值。
        */
        /*
        iterate方法接受一个初始值（在这里是0），还有一个依次应用在每个产生的新值上的
        Lambda（UnaryOperator<t>类型）。这里，我们使用Lambda n -> n + 2，返回的是前一个元
        素加上2。
        一般来说，在需要依次生成一系列值的时候应该使用iterate，比如一系列日期：1月31日，2月1日，
         */
        Stream.iterate(0, n -> n + 2)
              .limit(10)
              .forEach(System.out::println);

        //斐波纳契元组序列  fibonnaci with iterate
        Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1],t[0] + t[1]})
              .limit(10)
              .forEach(t -> System.out.println("(" + t[0] + ", " + t[1] + ")"));
        
        Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1],t[0] + t[1]})
              .limit(10)
              . map(t -> t[0])  
              .forEach(System.out::println);

        //generate不是依次对每个新生成的值应用函数的。它接受一个Supplier<T>类型的Lambda提供新的值。
        // random stream of doubles with Stream.generate
        Stream.generate(Math::random)               //Math.Random静态方法被用作新值生成器。
              .limit(10)                            //这段代码将生成一个流，其中有10个0到1之间的随机双精度数。
              .forEach(System.out::println);
 
        /*
        我们使用的供应源（指向Math.random的方法引用）是无状态的：它不会在任何地方记录任何值，以备以后计算使用。
        但供应源不一定是无状态的。你可以创建存储状态的供应源，它可以修改状态，并在为流生成下一个值时使用。举个
        例子，我们将展示如何利用generate创建测验5.4中的斐波纳契数列，这样你就可以和用
        iterate方法的办法比较一下。但很重要的一点是，在并行代码中使用有状态的供应源是不安全
        的。因此下面的代码仅仅是为了内容完整，应尽量避免使用！
         */
        // stream of 1s with Stream.generate
        IntStream.generate(() -> 1)         //使用IntStream说明避免装箱操作的代码，全是1的无限流
                .limit(5)
                .forEach(System.out::println);


        IntStream.generate(new IntSupplier(){
            /*
            但这里使用的匿名类和Lambda的区别在于，匿名类可以通过字段定义状态，而状态又可以用
            getAsInt方法来修改。这是一个副作用的例子。你迄今见过的所有Lambda都是没有副作用的；
            它们没有改变任何状态
             */
            public int getAsInt(){//generate方法将使用给定的供应源，并反复调用getAsInt方法，而这个方法总是返回2。
                return 2;
            }
        }).limit(5)
          .forEach(System.out::println);
   
        //斐波纳契元组序列
        IntSupplier fib = new IntSupplier(){
            /*
            创建了一个IntSupplier的实例。此对象有可变的状态：它在两个实例变量中
            记录了前一个斐波纳契项和当前的斐波纳契项。getAsInt在调用时会改变对象的状态，由此在
            每次调用时产生新的值。相比之下，使用iterate的方法则是纯粹不变的：它没有修改现有状态，
            但在每次迭代时会创建新的元组。你将在第7章了解到，你应该始终采用不变的方法，以便并行
            处理流，并保持结果正确。
            你不能对无限流做排序或归约，因为所有元素都需要处理，而这永远也完不成！
             */
                  private int previous = 0;
                  private int current = 1;
                  public int getAsInt(){
                      int nextValue = this.previous + this.current;
                      this.previous = this.current;
                      this.current = nextValue;
                      return this.previous;
                  }
              };
         IntStream.generate(fib).limit(10).forEach(System.out::println);





    }
}
